[스프링] 빈 스코프 - 싱글톤, 프로토타입

업데이트:



✅ 빈(Bean) 스코프

빈 스코프는 우리가 빈을 생성할 때, 빈을 공유하기 위한 인스턴스를 하나만 만들지(싱글턴 스코프), 아니면 빈이 요청될 때마다 새로운 빈 인스턴스를 생성할지(프로토타입 스코프) 여부를 제어하고자 할때 사용하는 개념이다.

빈의 스코프를 정의하는 방식은 스프링 버전에 따라 다를 수 있고 여러가지가 있지만 대표적으로 두가지 방식이 존재한다. 첫번째로는 **설정정보를 담고있는 XML파일에서 엘리먼트의 scope속성을 사용하는 방법**이 있다. 두번째로는 **`@Scope` 어노테이션을 활용해 빈 스코프를 정의해주는 방법**이 있다.

위와 같은 방식으로 빈 스코프를 따로 정의해주지 않았다면 빈 스코프는 자동으로 싱글턴 스코프를 가지게 된다.

빈 스코프의 종류는 Singleton, Prototype, Request, Session, Application 등이 있는데 우리는 자주 사용하는 Singleton과 Prototype에 대해서만 다루도록 하겠다.





✅ 싱글턴 (Singleton) 스코프

싱글턴 스코프는 XML파일에 정의된 모든 빈의 디폴트 스코프이다. 싱글턴 스코프 빈의 인스턴스는 스프링 컨테이너가 생성될 때 함께 생성되고, 스프링 컨테이너가 파괴될 때 함께 파괴된다. 또한 싱글턴 스코프 빈은 호출이 한번되든 여러번되든 상관없이 단일 스프링 컨테이너 내에서는 단 한개만 생성되고, 그 빈에 의존하는 모든 빈에 유일한 인스턴스를 공유한다.

너무너무 중요한 설명이니 위의 내용을 완벽하게 이해하고 넘어가야만 한다. xml방식과 클래스방식으로 설정정보를 저장하는 두가지 예제를 통해 좀더 자세히 알아보자.



📌 XML 파일로 bean 설정

// bankapp이라는 프로젝트의 applicationContext.xml

<beans ...>  
	<bean id="controller" class="bankapp.controller.FixedDepositControllerImpl">  
		<property name="fixedDepositService" ref="service" />  
	</bean>  
  
	<bean id="service" class="bankapp.service.FixedDepositServiceImpl">  
		<property name="fixedDepositDao" ref="dao"/>  
	</bean>  
  
	<bean id="dao" class="bankapp.dao.FixedDepositDaoImpl" />
	...
</beans>

controller, service, dao 빈은 스코프 지정을 해주지 않았기 때문에 싱글턴 스코프를 가진다. 때문에 스프링 컨테이너는 FixedDepositControllerImpl, FixedDepositServiceImpl, FixedDepositDaoImpl 클래스의 인스턴스를 단 하나씩만 만들게 된다.




📌 자바 클래스로 bean 설정

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig {

    @Bean
    @Scope("singleton")
    public FixedDepositController fixedDepositController() {
        FixedDepositControllerImpl controller = new FixedDepositControllerImpl();
        controller.setFixedDepositService(fixedDepositService());
        return controller;
    }

    @Bean
    @Scope("singleton")
    public FixedDepositService fixedDepositService() {
        FixedDepositServiceImpl service = new FixedDepositServiceImpl();
        service.setFixedDepositDao(fixedDepositDao());
        return service;
    }

    @Bean
    @Scope("singleton")
    public FixedDepositDao fixedDepositDao() {
        return new FixedDepositDaoImpl();
    }

    // 다른 빈 정의 및 설정...

}

xml의 <bean> 형태로 설정정보를 저장할 수도 있지만 설정정보를 담당하는 새로운 클래스를 만들수도 있다. AppConfig 라는 클래스를 만들고 @Configuration 처리를 하면 설정정보를 담당하는 클래스로 지정된다. 이후 각각의 빈에 @Scope()와 함께 "singleton" , "prototype" 등 값을 넣어주면 빈 스코프를 지정할 수 있다.



📌 싱글턴 스코프 테스트

public class SingletonTest {

	private static ApplicationContext context;  
  
	@BeforeClass  
	public static void init() {  
		context = new ClassPathXmlApplicationContext(  
		"classpath:META-INF/spring/applicationContext.xml");  
	}  
  
	@Test  
	public void testInstances() {  
		FixedDepositController controller1 
		= (FixedDepositController)context.getBean("controller");
		
		FixedDepositController controller2 
		= (FixedDepositController)context.getBean("controller");  
  
		assertSame("Different FixedDepositController instances", controller1,controller2);
	}
	...
}

ApplicationContext를 하나 생성하고 FixedDepositController 클래스의 서로 다른 객체 두개(controller1, controller2)를 생성했다. 각각의 객체는 getBean() 메소드를 통해 controller 라는 이름을 가진 빈을 가져온다. 그리고 이를 assertSame 메소드를 통해 같은 값을 반환하는지 확인했다. 이러한 테스트코드를 실행하면 오류없이 잘 작동하는 것을 볼 수 있다. 정리하면 서로 다른 객체에서 각자 빈을 호출했을 때 동일한 빈을 가져왔음을 의미하고 이는 싱글턴 스코프 빈이 잘 적용됐다는 뜻이다.



위 그림을 보면 싱글턴 스코프 빈을 여러번 요청해도 항상 동일한 빈을 반환하는 것을 알 수 있다.




📌 의존관계 빈들의 싱글턴 스코프

싱글턴 스코프 빈 인스턴스는 의존관계에 있는 모든 빈 사이에서도 공유된다. 바로 예제를 통해 살펴보자.

public class SingletonTest {  
private static ApplicationContext context;  
  
	@BeforeClass  
	public static void init() {  
		context = new ClassPathXmlApplicationContext(  
		"classpath:META-INF/spring/applicationContext.xml");  
	}  
  
	@Test  
	public void testReference() {  
		FixedDepositController controller 
		= (FixedDepositController) context.getBean("controller");
		
		FixedDepositDao fixedDepositDao1 
		= controller.getFixedDepositService().getFixedDepositDao();
		 
		FixedDepositDao fixedDepositDao2 
		= (FixedDepositDao) context.getBean("dao");  
  
		assertSame("Different FixedDepositDao instances",
		fixedDepositDao1, fixedDepositDao2);
	}  
}

그림에 기반해서 코드를 살펴보면, FixedDepositController빈이 참조하는 FixedDepositDao 인스턴스를 읽고(fixedDepositDao1), 이후 getBean() 메서드를 사용해 FixedDepositDao 빈의 다른 인스턴스를 읽었다(fixedDepositDao2). 테스트를 실행해보면 두 인스턴스가 같음을 알 수 있다.




📌 싱글턴 스코프는 같은 스프링 컨테이너 내에서만 유지된다!

앞서 계속 싱글턴 스코프 빈은 인스턴스를 한개만 생성하고 호출될 때마다 해당 빈을 공유한다고 설명했다. 그러나 이는 하나의 스프링 컨테이너 내에서만 해당하는 얘기이다. 스프링 컨테이너를 두개이상 만들고 각 스프링 컨테이너마다 싱글턴 빈 인스턴스를 만들면, 각 컨테이너의 싱글턴 인스턴스는 서로 다른 빈이 된다.





✅ 프로토타입 (Prototype) 스코프

프로토타입 스코프 빈이 싱글턴 스코프 빈과 다른점은 스프링 컨테이너가 항상 프로토타입 스코프 빈의 새로운 인스턴스를 반환한다는 점이다.

// ApplicationContext.xml
<bean id="fixedDepositDetails"  
	class="sample.spring.chapter02.bankapp.domain.FixedDepositDetails" 
	scope="prototype" />  
</beans>

정의하는 방법은 싱글턴과 동일하다. <bean> 엘리먼트에 scope="prototype" 을 추가해주면 된다. 어려운 개념은 없다. 싱글턴 스코프 빈은 단 한개의 인스턴스만 생성되는 것이고, 프로토타입 스코프 빈은 호출될 때마다 새로운 인스턴스를 생성하는 것이다.

댓글남기기